package jrandom.wizards;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Vector;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

import org.eclipse.ui.dialogs.ContainerCheckedTreeViewer;

public class JRandomTestCaseWizardPage2 extends WizardPage {
	
	private JRandomTestCaseWizardPage1 jPage1;
	
	private ContainerCheckedTreeViewer methodsTree;
	private IMethod[] checkedMethods;
	private Text numberText;
	
	private boolean isARTSelected;
	private boolean isMethodReady;
	private boolean isNumberReady;

	public JRandomTestCaseWizardPage2(JRandomTestCaseWizardPage1 page1) {
		super("wizardPage2");
		setTitle("Test Methods Selection");
		setDescription("Choose available methods and create test cases.");
		setPageComplete(false);
		this.jPage1 = page1;
	}

	@Override
	public void createControl(Composite parent) {
		Composite composite = new Composite(parent, SWT.NULL);
		
		GridLayout layout = new GridLayout();
		int nColumns = 2;
		
		layout.numColumns = nColumns;
		composite.setLayout(layout);

		createMethodsTreeControls(composite, nColumns);
		createSpacer(composite, nColumns);
		createGroupControls(composite, nColumns);

		
		// very important code below
		setControl(composite);
	}
	
	private void createGroupControls(Composite composite, int nColumns) {
		Group group = new Group(composite, SWT.NONE);
		group.setText("Test Case Set");
		GridData gridData = new GridData();
		gridData.horizontalAlignment = GridData.FILL;
		gridData.horizontalSpan = nColumns;
		group.setLayoutData(gridData);
		GridLayout gridLayout = new GridLayout(2, false);
		group.setLayout(gridLayout);
		
		createNumberControls(group, 2);
		createRTorARTControls(group, 2);
	}

	private void createRTorARTControls(Composite composite, int nColumns) {
		isARTSelected = true;
		Button htmlButton = new Button(composite, SWT.RADIO);
		htmlButton.setText("Random Testing");
		htmlButton.setSelection(!isARTSelected);
		htmlButton.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, false, false, 1, 1));
		
		Button xmlButton = new Button(composite, SWT.RADIO);
		xmlButton.setText("Adaptive Random Testing");
		xmlButton.setSelection(isARTSelected);
		xmlButton.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, false, false, 1, 1));
		xmlButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				isARTSelected = ((Button) e.widget).getSelection();
			}
		});
	}

	private void createNumberControls(Composite composite, int nColumns) {
		Label label = new Label(composite, SWT.NONE);
		label.setText("Number: ");
		numberText = new Text(composite, SWT.BORDER | SWT.SINGLE);
		GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
		gridData.horizontalSpan = nColumns - 1;
		numberText.setLayoutData(gridData);
		numberText.setText("100");
		numberText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				numberTextChanged();
			}
		});
	}

	private void createMethodsTreeControls(Composite composite, int nColumns) {
		Label label = new Label(composite, SWT.LEFT | SWT.WRAP);
		label.setFont(composite.getFont());
		label.setText("Available Methods:"); 
		GridData gd = new GridData();
		gd.horizontalSpan = nColumns;
		label.setLayoutData(gd);

		methodsTree = new ContainerCheckedTreeViewer(composite, SWT.BORDER);
		gd = new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
		gd.heightHint = 180;
		gd.widthHint = 180;
		methodsTree.getTree().setLayoutData(gd);

		methodsTree.setLabelProvider(new JavaElementLabelProvider());
		methodsTree.setAutoExpandLevel(2);
		methodsTree.addCheckStateListener(new ICheckStateListener() {
			public void checkStateChanged(CheckStateChangedEvent event) {
				checkedStateChanged();
			}	
		});
		methodsTree.addFilter(new ViewerFilter() {
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				if (element instanceof IMethod) {
					IMethod method = (IMethod) element;
					return !method.getElementName().equals("<clinit>"); //$NON-NLS-1$
				}
				return true;
			}
		});

	}
	
	private void createSpacer(Composite composite, int nColumns) {
		Label spacer = new Label(composite, SWT.NONE);
		GridData data = new GridData();
		data.horizontalSpan = nColumns;
		data.horizontalAlignment = GridData.FILL;
		data.verticalAlignment = GridData.BEGINNING;
		data.heightHint = 4;
		spacer.setLayoutData(data);
	}
	
	private void numberTextChanged() {
		String num = numberText.getText();
		isNumberReady = false;
		
		try {
			if (Integer.parseInt(num) < 1 || Integer.parseInt(num) > 1000 ) {
				updatePageStatus("The number of Test Cases is out of range, it should be between 1 and 1000.");
				return;
			}
		} catch (Exception ex) {
			updatePageStatus("The number input is invalid, it should be an integer.");
			return;
		}
		
		isNumberReady = true;
		
		if (isMethodReady)
			updatePageStatus(null);
		else
			checkedStateChanged();
	}
	
	private void checkedStateChanged() {
		Object[] checkedObjects = methodsTree.getCheckedElements();
		int methodCount = 0;
		isMethodReady = false;
		
		for (int i = 0; i < checkedObjects.length; i++) {
			if (checkedObjects[i] instanceof IMethod)
				methodCount++;
		}
		if (methodCount == 0) {
			updatePageStatus("Please choose a method to test.");
			return;
		}
		
		if (methodCount > 1) {
			updatePageStatus("We now just support to test only one method once.");
			return;
		}
		
		checkedMethods = new IMethod[methodCount];
		for (int i = 0, j = 0; i < checkedObjects.length; i++) {
			if (checkedObjects[i] instanceof IMethod) {
				checkedMethods[j] = (IMethod)checkedObjects[i];
				j++;
			}
		}
		isMethodReady = true;
		
		if (isNumberReady)
			updatePageStatus(null);
		else
			numberTextChanged();
	}
	
	private void updatePageStatus(String message) {
		setErrorMessage(message);
		setPageComplete(message == null);
	}
	
	public void setVisible(boolean visible) {
		super.setVisible(visible);
		if (visible) {
			IType classToTest = jPage1.getClassToTest();
			if (classToTest == null) {
				return;
			}
			
			ArrayList<IType> types = null;
			try {
				ITypeHierarchy hierarchy = classToTest.newSupertypeHierarchy(null);
				IType[] superTypes;
				if (classToTest.isClass())
					superTypes = hierarchy.getAllSuperclasses(classToTest);
				else if (classToTest.isInterface())
					superTypes = hierarchy.getAllSuperInterfaces(classToTest);
				else
					superTypes = new IType[0];
				types = new ArrayList<IType>(superTypes.length+1);
				types.add(classToTest);
				types.addAll(Arrays.asList(superTypes));
			} catch(JavaModelException e) {
				e.printStackTrace();
			}
			if (types == null)
				types = new ArrayList<IType>();
			methodsTree.setContentProvider(new MethodsTreeContentProvider(types.toArray()));
			methodsTree.setInput(types.toArray());
			methodsTree.setSelection(new StructuredSelection(classToTest), true);
			
			methodsTree.getControl().setFocus();
		}
	}
	
	/**
	 * Returns all checked methods in the methods tree.
	 * 
	 * @return the checked methods
	 */
	public IMethod[] getCheckedMethods() {
		return checkedMethods;
	}
	
	public int getTestCaseNumber() {
		return Integer.parseInt(numberText.getText());
	}
	
	public boolean isARTSelected() {
		return isARTSelected;
	}

	private static class MethodsTreeContentProvider implements ITreeContentProvider {
		private Object[] allObjects;
		private IMethod[] allMethods;
		private final Object[] fEmpty = new Object[0];

		public MethodsTreeContentProvider(Object[] types) {
			allObjects = types;
			Vector<IMethod> methods = new Vector<IMethod>();
			for (int i = types.length-1; i > -1; i--) {
				Object object = types[i];
				if (object instanceof IType) {
					IType type = (IType) object;
					try {
						IMethod[] currMethods = type.getMethods();
						for_currMethods:
						for (int j = 0; j < currMethods.length; j++) {
							IMethod currMethod = currMethods[j];
							int flags = currMethod.getFlags();
							// Filter the methods that is private or synthetic
							if (!Flags.isPrivate(flags) && !Flags.isSynthetic(flags)) {
								for (int k = 0; k < methods.size(); k++) {
									IMethod m = ((IMethod)methods.get(k));
									if (m.getElementName().equals(currMethod.getElementName())
										&& m.getSignature().equals(currMethod.getSignature())) {
										methods.set(k,currMethod);
										continue for_currMethods;
									}
								}
								methods.add(currMethod);
							}
						}
					} catch (JavaModelException e) {
						e.printStackTrace();
					}
				}
			}
			allMethods = new IMethod[methods.size()];
			methods.copyInto(allMethods);
		}
		
		/*
		 * @see ITreeContentProvider#getChildren(Object)
		 */
		public Object[] getChildren(Object parentElement) {
			if (parentElement instanceof IType) {
				IType parentType= (IType)parentElement;
				ArrayList<IMethod> result= new ArrayList<IMethod>(allMethods.length);
				for (int i= 0; i < allMethods.length; i++) {
					if (allMethods[i].getDeclaringType().equals(parentType)) {
						result.add(allMethods[i]);
					}
				}
				return result.toArray();
			}
			return fEmpty;
		}

		/*
		 * @see ITreeContentProvider#getParent(Object)
		 */
		public Object getParent(Object element) {
			if (element instanceof IMethod) 
				return ((IMethod)element).getDeclaringType();
			return null;
		}

		/*
		 * @see ITreeContentProvider#hasChildren(Object)
		 */
		public boolean hasChildren(Object element) {
			return getChildren(element).length > 0;
		}

		/*
		 * @see IStructuredContentProvider#getElements(Object)
		 */
		public Object[] getElements(Object inputElement) {
			return allObjects;
		}

		/*
		 * @see IContentProvider#dispose()
		 */
		public void dispose() {
		}

		/*
		 * @see IContentProvider#inputChanged(Viewer, Object, Object)
		 */
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}
		
//		public IMethod[] getAllMethods() {
//			return allMethods;
//		}
	}
}
